(function($) {

	var version = '1.4.10',
		defaults = {
			exclude: [],
			excludeWithin:[],
			offset: 0,

			// one of 'top' or 'left'
			direction: 'top',

			// jQuery set of elements you wish to scroll (for $.smoothScroll).
			//  if null (default), $('html, body').firstScrollable() is used.
			scrollElement: null,

			// only use if you want to override default behavior
			scrollTarget: null,

			// fn(opts) function to be called before scrolling occurs.
			// `this` is the element(s) being scrolled
			beforeScroll: function() {},

			// fn(opts) function to be called after scrolling occurs.
			// `this` is the triggering element
			afterScroll: function() {},
			easing: 'swing',
			speed: 400,

			// coefficient for "auto" speed
			autoCoefficent: 2
		},

		getScrollable = function(opts) {
			var scrollable = [],
				scrolled = false,
				dir = opts.dir && opts.dir == 'left' ? 'scrollLeft' : 'scrollTop';

			this.each(function() {

				if (this == document || this == window) { return; }
				var el = $(this);
				if ( el[dir]() > 0 ) {
					scrollable.push(this);
				} else {
					// if scroll(Top|Left) === 0, nudge the element 1px and see if it moves
					el[dir](1);
					scrolled = el[dir]() > 0;
					if ( scrolled ) {
						scrollable.push(this);
					}
					// then put it back, of course
					el[dir](0);
				}
			});

			// If no scrollable elements, fall back to <body>,
			// if it's in the jQuery collection
			// (doing this because Safari sets scrollTop async,
			// so can't set it to 1 and immediately get the value.)
			if (!scrollable.length) {
				this.each(function(index) {
					if (this.nodeName === 'BODY') {
						scrollable = [this];
					}
				});
			}

			// Use the first scrollable element if we're calling firstScrollable()
			if ( opts.el === 'first' && scrollable.length > 1 ) {
				scrollable = [ scrollable[0] ];
			}

			return scrollable;
		},
		isTouch = 'ontouchend' in document;

	$.fn.extend({
		scrollable: function(dir) {
			var scrl = getScrollable.call(this, {dir: dir});
			return this.pushStack(scrl);
		},
		firstScrollable: function(dir) {
			var scrl = getScrollable.call(this, {el: 'first', dir: dir});
			return this.pushStack(scrl);
		},

		smoothScroll: function(options) {
			options = options || {};
			var opts = $.extend({}, $.fn.smoothScroll.defaults, options),
				locationPath = $.smoothScroll.filterPath(location.pathname);

			this
				.unbind('click.smoothscroll')
				.bind('click.smoothscroll', function(event) {
					var link = this,
						$link = $(this),
						exclude = opts.exclude,
						excludeWithin = opts.excludeWithin,
						elCounter = 0, ewlCounter = 0,
						include = true,
						clickOpts = {},
						hostMatch = ((location.hostname === link.hostname) || !link.hostname),
						pathMatch = opts.scrollTarget || ( $.smoothScroll.filterPath(link.pathname) || locationPath ) === locationPath,
						thisHash = escapeSelector(link.hash);

					if ( !opts.scrollTarget && (!hostMatch || !pathMatch || !thisHash) ) {
						include = false;
					} else {
						while (include && elCounter < exclude.length) {
							if ($link.is(escapeSelector(exclude[elCounter++]))) {
								include = false;
							}
						}
						while ( include && ewlCounter < excludeWithin.length ) {
							if ($link.closest(excludeWithin[ewlCounter++]).length) {
								include = false;
							}
						}
					}

					if ( include ) {
						event.preventDefault();

						$.extend( clickOpts, opts, {
							scrollTarget: opts.scrollTarget || thisHash,
							link: link
						});

						$.smoothScroll( clickOpts );
					}
				});

			return this;
		}
	});

	$.smoothScroll = function(options, px) {
		var opts, $scroller, scrollTargetOffset, speed,
			scrollerOffset = 0,
			offPos = 'offset',
			scrollDir = 'scrollTop',
			aniProps = {},
			aniOpts = {},
			scrollprops = [];


		if (typeof options === 'number') {
			opts = $.fn.smoothScroll.defaults;
			scrollTargetOffset = options;
		} else {
			opts = $.extend({}, $.fn.smoothScroll.defaults, options || {});
			if (opts.scrollElement) {
				offPos = 'position';
				if (opts.scrollElement.css('position') == 'static') {
					opts.scrollElement.css('position', 'relative');
				}
			}
		}

		opts = $.extend({link: null}, opts);
		scrollDir = opts.direction == 'left' ? 'scrollLeft' : scrollDir;

		if ( opts.scrollElement ) {
			$scroller = opts.scrollElement;
			scrollerOffset = $scroller[scrollDir]();
		} else {
			$scroller = $('html, body').firstScrollable();
		}

		// beforeScroll callback function must fire before calculating offset
		opts.beforeScroll.call($scroller, opts);

		scrollTargetOffset = (typeof options === 'number') ? options :
			px ||
				( $(opts.scrollTarget)[offPos]() &&
					$(opts.scrollTarget)[offPos]()[opts.direction] ) ||
				0;

		aniProps[scrollDir] = scrollTargetOffset + scrollerOffset + opts.offset;
		speed = opts.speed;

		// automatically calculate the speed of the scroll based on distance / coefficient
		if (speed === 'auto') {

			// if aniProps[scrollDir] == 0 then we'll use scrollTop() value instead
			speed = aniProps[scrollDir] || $scroller.scrollTop();

			// divide the speed by the coefficient
			speed = speed / opts.autoCoefficent;
		}

		aniOpts = {
			duration: speed,
			easing: opts.easing,
			complete: function() {
				opts.afterScroll.call(opts.link, opts);
			}
		};

		if (opts.step) {
			aniOpts.step = opts.step;
		}

		if ($scroller.length) {
			$scroller.stop().animate(aniProps, aniOpts);
		} else {
			opts.afterScroll.call(opts.link, opts);
		}
	};

	$.smoothScroll.version = version;
	$.smoothScroll.filterPath = function(string) {
		return string
			.replace(/^\//,'')
			.replace(/(index|default).[a-zA-Z]{3,4}$/,'')
			.replace(/\/$/,'');
	};

// default options
	$.fn.smoothScroll.defaults = defaults;

	function escapeSelector (str) {
		return str.replace(/(:|\.)/g,'\\$1');
	}

})(jQuery);